package browser

import (
	"context"

	uurl "net/url"

	"gitee.com/baixudong/gospider/bs4"
	"gitee.com/baixudong/gospider/tools"
)

type Dom struct {
	baseUrl *uurl.URL
	webSock *webSock
	nodeId  int64
}
type Iframe struct {
	webSock   *webSock
	iframeId  string
	iframeUrl string
}

func (obj *Iframe) Html(ctx context.Context) (*bs4.Client, error) {
	rs, err := obj.webSock.send(ctx, commend{
		Method: "Page.getResourceContent",
		Params: map[string]any{
			"frameId": obj.iframeId,
			"url":     obj.iframeUrl,
		},
	})
	if err != nil {
		return nil, err
	}
	content := tools.Any2json(rs.Result).Get("content").String()
	if tools.Any2json(rs.Result).Get("base64Encoded").Bool() {
		con, err := tools.Base64Decode(content)
		if err != nil {
			return nil, err
		}
		content = tools.BytesToString(con)
	}
	return bs4.NewClient(content, obj.iframeUrl), nil
}

func (obj *Dom) Html(ctx context.Context, contents ...string) (*bs4.Client, error) {
	if len(contents) > 0 {
		_, err := obj.webSock.send(ctx, commend{
			Method: "DOM.setOuterHTML",
			Params: map[string]any{
				"nodeId":    obj.nodeId,
				"outerHTML": contents[0],
			},
		})
		return nil, err
	}
	return obj.html(ctx)
}
func (obj *Dom) iframes(ctx context.Context) (map[string]*Iframe, error) {
	rs, err := obj.webSock.send(ctx, commend{
		Method: "Page.getFrameTree",
	})
	if err != nil {
		return nil, err
	}
	results := map[string]*Iframe{}
	for _, ifram := range tools.Any2json(rs.Result).Get("frameTree.childFrames").Array() {
		frameId := ifram.Get("frame.id").String()
		frameUrl := ifram.Get("frame.url").String()
		results[frameUrl] = &Iframe{
			iframeId:  frameId,
			iframeUrl: frameUrl,
			webSock:   obj.webSock,
		}
	}
	return results, nil
}
func (obj *Dom) html(ctx context.Context) (*bs4.Client, error) {
	rs, err := obj.webSock.send(ctx, commend{
		Method: "DOM.getOuterHTML",
		Params: map[string]any{
			"nodeId": obj.nodeId,
		},
	})
	if err != nil {
		return nil, err
	}
	html := bs4.NewClient(rs.Result["outerHTML"].(string), obj.baseUrl.String())
	iframes := html.Finds("iframe")
	if len(iframes) > 0 {
		pageFrams, err := obj.iframes(ctx)
		if err != nil {
			return nil, err
		}
		for _, ifram := range iframes {
			ju, err := tools.UrlJoin(obj.baseUrl.String(), ifram.Get("src"))
			if err != nil {
				return nil, err
			}
			frame, ok := pageFrams[ju]
			if ok {
				dh, err := frame.Html(ctx)
				if err != nil {
					return nil, err
				}
				ifram.Html(dh.Html())
			}
		}
	}
	return html, nil
}

func (obj *Dom) Focus(ctx context.Context) error {
	_, err := obj.webSock.send(ctx, commend{
		Method: "DOM.focus",
		Params: map[string]any{
			"nodeId": obj.nodeId,
		},
	})
	return err
}

func (obj *Dom) SendText(ctx context.Context, text string) error {
	err := obj.Focus(ctx)
	if err != nil {
		return err
	}
	for _, chr := range text {
		err = obj.sendChar(ctx, chr)
		if err != nil {
			return err
		}
	}
	return nil
}

func (obj *Dom) sendChar(ctx context.Context, chr rune) error {
	_, err := obj.webSock.send(ctx, commend{
		Method: "Input.dispatchKeyEvent",
		Params: map[string]any{
			"type": "keyDown",
			"key":  "Unidentified",
		},
	})
	if err != nil {
		return err
	}
	_, err = obj.webSock.send(ctx, commend{
		Method: "Input.dispatchKeyEvent",
		Params: map[string]any{
			"type":           "char",
			"key":            "Unidentified",
			"text":           string(chr),
			"unmodifiedText": string(chr),
		},
	})
	if err != nil {
		return err
	}
	_, err = obj.webSock.send(ctx, commend{
		Method: "Input.dispatchKeyEvent",
		Params: map[string]any{
			"type": "keyUp",
			"key":  "Unidentified",
		},
	})
	return err
}
func (obj *Dom) QuerySelector(ctx context.Context, selector string) (*Dom, error) {
	rs, err := obj.webSock.send(ctx, commend{
		Method: "DOM.querySelector",
		Params: map[string]any{
			"nodeId":   obj.nodeId,
			"selector": selector,
		},
	})
	if err != nil {
		return nil, err
	}
	nodeId := int64(rs.Result["nodeId"].(float64))
	if nodeId == 0 {
		return nil, nil
	}
	dom := Dom{
		webSock: obj.webSock,
		nodeId:  nodeId,
	}
	return &dom, nil
}
func (obj *Dom) QuerySelectorAll(ctx context.Context, selector string) ([]*Dom, error) {
	rs, err := obj.webSock.send(ctx, commend{
		Method: "DOM.querySelectorAll",
		Params: map[string]any{
			"nodeId":   obj.nodeId,
			"selector": selector,
		},
	})
	if err != nil {
		return nil, err
	}
	doms := []*Dom{}
	for _, nodeId := range tools.Any2json(rs.Result["nodeIds"]).Array() {
		doms = append(doms, &Dom{
			webSock: obj.webSock,
			nodeId:  nodeId.Int(),
		})
	}
	return doms, nil
}

type Point struct {
	X float64 `json:"x"`
	Y float64 `json:"y"`
}
type BoxData struct {
	Point  Point
	Point2 Point
	Center Point
	Width  float64
	Height float64
}

func (obj *Dom) Box(ctx context.Context) (BoxData, error) {
	rs, err := obj.webSock.send(ctx, commend{
		Method: "DOM.getBoxModel",
		Params: map[string]any{
			"nodeId": obj.nodeId,
		},
	})
	if err != nil {
		return BoxData{}, err
	}
	jsonData := tools.Any2json(rs.Result["model"])
	content := jsonData.Get("content").Array()
	point := Point{
		X: content[0].Float(),
		Y: content[1].Float(),
	}
	point2 := Point{
		X: content[4].Float(),
		Y: content[5].Float(),
	}
	boxData := BoxData{
		Width:  jsonData.Get("width").Float(),
		Height: jsonData.Get("height").Float(),
		Point:  point,
		Point2: point2,
	}
	boxData.Center = Point{
		X: boxData.Point.X + boxData.Width/2,
		Y: boxData.Point.Y + boxData.Height/2,
	}
	return boxData, nil
}
